using System;

//
// * Copyright (c) 2002-2009 "Neo Technology,"
// *     Network Engine for Objects in Lund AB [http://neotechnology.com]
// *
// * This file is part of Neo4j.
// * 
// * Neo4j is free software: you can redistribute it and/or modify
// * it under the terms of the GNU Affero General Public License as
// * published by the Free Software Foundation, either version 3 of the
// * License, or (at your option) any later version.
// * 
// * This program is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// * GNU Affero General Public License for more details.
// * 
// * You should have received a copy of the GNU Affero General Public License
// * along with this program. If not, see <http://www.gnu.org/licenses/>.
// 
namespace org.neo4j.kernel.impl.nioneo.store
{


///
/// <summary> * This class contains the references to the "NodeStore,RelationshipStore,
/// * PropertyStore and RelationshipTypeStore". NeoStore doesn't actually "store"
/// * anything but extends the AbstractStore for the "type and version" validation
/// * performed in there. </summary>
/// 
	public class NeoStore : AbstractStore
	{
	// neo store version, store should end with this string
	// (byte encoded)
		private const string VERSION = "NeoStore v0.9.5";

	// 3 longs in header (long + in use), time | random | version
		private const int RECORD_SIZE = 9;

		private NodeStore nodeStore;
		private PropertyStore propStore;
		private RelationshipStore relStore;
		private RelationshipTypeStore relTypeStore;

		private readonly int REL_GRAB_SIZE;

		public NeoStore<T1>(Map<T1> config) : base((string) config.get("neo_store"), config)
		{
			if (getConfig() != null)
			{
				string grabSize = (string) getConfig().get("relationship_grab_size");
				if (grabSize != null)
				{
					REL_GRAB_SIZE = Convert.ToInt32(grabSize);
				}
				else
				{
					REL_GRAB_SIZE = 100;
				}
			}
			else
			{
				REL_GRAB_SIZE = 100;
			}
		}

		public NeoStore(string fileName) : base(fileName)
		{
			REL_GRAB_SIZE = 100;
		}

///    
///     <summary> * Initializes the node,relationship,property and relationship type stores. </summary>
///     
		protected internal override void initStorage()
		{
			relTypeStore = new RelationshipTypeStore(getStorageFileName() + ".relationshiptypestore.db", getConfig());
			propStore = new PropertyStore(getStorageFileName() + ".propertystore.db", getConfig());
			relStore = new RelationshipStore(getStorageFileName() + ".relationshipstore.db", getConfig());
			nodeStore = new NodeStore(getStorageFileName() + ".nodestore.db", getConfig());
		}

///    
///     <summary> * Closes the node,relationship,property and relationship type stores. </summary>
///     
		protected internal override void closeStorage()
		{
			if (relTypeStore != null)
			{
				relTypeStore.Close();
				relTypeStore = null;
			}
			if (propStore != null)
			{
				propStore.Close();
				propStore = null;
			}
			if (relStore != null)
			{
				relStore.Close();
				relStore = null;
			}
			if (nodeStore != null)
			{
				nodeStore.Close();
				nodeStore = null;
			}
		}

		public virtual void flushAll()
		{
			relTypeStore.flushAll();
			propStore.flushAll();
			relStore.flushAll();
			nodeStore.flushAll();
		}

		public virtual string getTypeAndVersionDescriptor()
		{
			return VERSION;
		}

		public override int getRecordSize()
		{
			return RECORD_SIZE;
		}

		private static readonly Random r = new Random(System.currentTimeMillis());

///    
///     <summary> * Creates the neo,node,relationship,property and relationship type stores.
///     *  </summary>
///     * <param name="fileName">
///     *            The name of store </param>
///     * <exception cref="IOException">
///     *             If unable to create stores or name null </exception>
///     
		public static void createStore(string fileName)
		{
			createEmptyStore(fileName, VERSION);
			NodeStore.createStore(fileName + ".nodestore.db");
			RelationshipStore.createStore(fileName + ".relationshipstore.db");
			PropertyStore.createStore(fileName + ".propertystore.db");
			RelationshipTypeStore.createStore(fileName + ".relationshiptypestore.db");
			NeoStore neoStore = new NeoStore(fileName);
		// created time | random long | backup version
			neoStore.nextId();
			neoStore.nextId();
			neoStore.nextId();
			long time = System.currentTimeMillis();
			neoStore.setCreationTime(time);
			neoStore.setRandomNumber(r.nextLong());
			neoStore.setVersion(0);
			neoStore.Close();
		}

		public virtual long getCreationTime()
		{
			return getRecord(0);
		}

		public virtual void setCreationTime(long time)
		{
			setRecord(0, time);
		}

		public virtual long getRandomNumber()
		{
			return getRecord(1);
		}

		public virtual void setRandomNumber(long nr)
		{
			setRecord(1, nr);
		}

		public virtual void setRecoveredStatus(bool status)
		{
			if (status)
			{
				setRecovered();
			}
			else
			{
				unsetRecovered();
			}
		}

		public virtual long getVersion()
		{
			return getRecord(2);
		}

		public virtual void setVersion(long version)
		{
			setRecord(2, version);
		}

		public virtual long incrementVersion()
		{
			long current = getVersion();
			setVersion(current + 1);
			return current;
		}

		private long getRecord(int id)
		{
			PersistenceWindow window = acquireWindow(id, OperationType.READ);
			try
			{
				Buffer buffer = window.getOffsettedBuffer(id);
				buffer.get();
				return buffer.getLong();
			}
			finally
			{
				releaseWindow(window);
			}
		}

		private void setRecord(int id, long @value)
		{
			PersistenceWindow window = acquireWindow(id, OperationType.WRITE);
			try
			{
				Buffer buffer = window.getOffsettedBuffer(id);
				buffer.put(Record.IN_USE.byteValue()).putLong(@value);
			}
			finally
			{
				releaseWindow(window);
			}
		}

///    
///     <summary> * Returns the node store.
///     *  </summary>
///     * <returns> The node store </returns>
///     
		public virtual NodeStore getNodeStore()
		{
			return nodeStore;
		}

///    
///     <summary> * The relationship store.
///     *  </summary>
///     * <returns> The relationship store </returns>
///     
		public virtual RelationshipStore getRelationshipStore()
		{
			return relStore;
		}

///    
///     <summary> * Returns the relationship type store.
///     *  </summary>
///     * <returns> The relationship type store </returns>
///     
		public virtual RelationshipTypeStore getRelationshipTypeStore()
		{
			return relTypeStore;
		}

///    
///     <summary> * Returns the property store.
///     *  </summary>
///     * <returns> The property store </returns>
///     
		public virtual PropertyStore getPropertyStore()
		{
			return propStore;
		}

		public override void makeStoreOk()
		{
			relTypeStore.makeStoreOk();
			propStore.makeStoreOk();
			relStore.makeStoreOk();
			nodeStore.makeStoreOk();
			base.makeStoreOk();
		}


		public virtual void rebuildIdGenerators()
		{
			relTypeStore.rebuildIdGenerators();
			propStore.rebuildIdGenerators();
			relStore.rebuildIdGenerators();
			nodeStore.rebuildIdGenerators();
			base.rebuildIdGenerators();
		}

		public virtual void updateIdGenerators()
		{
			this.updateHighId();
			relTypeStore.updateIdGenerators();
			propStore.updateIdGenerators();
			relStore.updateHighId();
			nodeStore.updateHighId();
		}

		protected internal override bool versionFound(string version)
		{
			if (!version.StartsWith("NeoStore"))
			{
			// non clean shutdown, need to do recover with right neo
				return false;
			}
			if (version.Equals("NeoStore v0.9.3"))
			{
				ByteBuffer buffer = ByteBuffer.wrap(new sbyte[3 * RECORD_SIZE]);
				long time = System.currentTimeMillis();
				long random = r.nextLong();
				buffer.put(Record.IN_USE.byteValue()).putLong(time);
				buffer.put(Record.IN_USE.byteValue()).putLong(random);
				buffer.put(Record.IN_USE.byteValue()).putLong(0);
				buffer.flip();
				try
				{
					getFileChannel().write(buffer, 0);
				}
				catch (IOException e)
				{
					throw new UnderlyingStorageException(e);
				}
				rebuildIdGenerator();
				closeIdGenerator();
				return true;
			}
			else if (version.Equals("NeoStore v0.9.4"))
			{
				rebuildIdGenerator();
				closeIdGenerator();
				return true;
			}
			throw new IllegalStoreVersionException("Store version [" + version + "]. Please make sure you are not running old Neo4j kernel " + " towards a store that has been created by newer version " + " of Neo4j.");
		}

		public virtual int getRelationshipGrabSize()
		{
			return REL_GRAB_SIZE;
		}
	}
}